# numpy实现神经网络
import numpy as np

def sigmoid(x):
    return 1/(1+np.exp(-x))

def dsigmoid(y):
    return y * (1.0 - y)

class MLP_NeuralNetwork:
    def __init__(self,input_,hidden,output):
        """
        :param input_ : number of input neurons
        :param hidden : number of hidden neurons
        :param output : number of output neurons
        """
        self.input=input_ + 1  # 加一个偏差项
        self.hidden=hidden
        self.output=output
        # 设置激活因子
        self.ai=[1.0] * self.input
        self.ah=[1.0] * self.hidden
        self.ao=[1.0] * self.output
        # 随机权重
        self.wi=np.random.randn(self.input,self.hidden)
        self.wo=np.random.randn(self.hidden,self.output)
        # 设置改变因子
        self.ci=np.zeros((self.input,self.hidden))
        self.co=np.zeros((self.hidden,self.output))
    
    def feedForward(self,inputs):
        if len(inputs) != self.input - 1:
            raise ValueError('输入层神经网络有错误')
        # 输入层跑起来
        for i in range(self.input-1):
            self.ai[i]=inputs[i]
        # 隐藏层跑起来
        for j in range(self.hidden):
            sum=0.0
            for i in range(self.input):
                sum+=self.ai[i]*self.wi[i][j]
            self.ah[j]=sigmoid(sum)
        # 输出层跑起来
        for k in range(self.output):
            sum =0.0
            for j in range(self.hidden):
                sum+=self.ah[j]*self.wo[j][k]
            self.ao[k]=sigmoid(sum)
        return self.ao[:]
    
    def backPropagate(self,targets,N):
        """
        :param targets : y values
        :param N : learning rate
        :return : update weights and current error
        """
        if len(targets) != self.output:
            raise ValueError('输出层的元的数量对不上 : 1,{} 2,{}'.format(len(targets),self.output))
        # 计算输出层的误差
        outputDeltas=[0.0] * self.output
        for k in range(self.output):
            error = -(targets[k]-self.ao[k])
            outputDeltas[k]=dsigmoid(self.ao[k]) * error
        
        # 计算隐藏层的误差
        hiddenDeltas=[0.0] * self.hidden
        for j in range(self.hidden):
            error=0.0
            for k in range(self.output):
                error+=outputDeltas[k] * self.wo[j][k]
            hiddenDeltas[j]=dsigmoid(self.ah[j]) * error
        
        # 更新隐藏层连接输出层的权重
        for j in range(self.hidden):
            for k in range(self.output):
                change=outputDeltas[k] * self.ah[j]
                self.wo[j][k]-=N * change + self.co[j][k]
                self.co[j][k]=change
        
        # 更新隐藏层与输入层的权重
        for i in range(self.input):
            for j in range(self.hidden):
                change=hiddenDeltas[j] * self.ai[i]
                self.wi[i][j]-=N * change + self.ci[i][j]
                self.ci[i][j]=change
        # 计算误差
        error=0.0
        for k in range(len(targets)):
            error += 0.5 * (targets[k] - self.ao[k]) ** 2
        return error
    
    def train(self,patterns,iterations=3000,N=0.0002):
        # N : learning rate
        for i in range(iterations):
            error=0.0
            for p in patterns:
                inputs=p[0]
                targets=p[1]
                self.feedForward(inputs)
                error-self.backPropagate(targets,N)
            if i % 500==0:
                print('error : ',error)

    def predict(self,x):
        """
        return list of predictions after traning algorithm
        """
        predictions=[]
        for p in x:
            predictions.append(self.feedForward(p))
        return predictions

#data=[[[1,2],[7,0]],[[2,4],[8,0]],[[3,6],[9,0]],[[4,8],[10,0]],[[5,8],[11,0]],[[3,4],[12,0]]]
#a=MLP_NeuralNetwork(2,2,2)
data=[[[1],[2]],[[3],[4]],[[5],[6]],[[7],[8]],[[9],[10]]]
a=MLP_NeuralNetwork(1,1,1)
a.train(data)

'''
结果：
error :  0.0
error :  0.0
error :  0.0
error :  0.0
error :  0.0
error :  0.0
'''